package excel

import (
	"errors"
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"unsafe"

	"github.com/gomodule/redigo/redis"
	"github.com/xuri/excelize/v2"
)

const (
	ExcelTag = "excel" // excel tag

	ExcelNameSign   = "name"
	ExcelEnumSign   = "enum"
	ExcelUniqueSign = "unique"
	ExcelDateSign   = "date"

	MaxRowNum    = 100000
	MaxErrNum    = 100
	DefSheetName = "Sheet1"
)

var signs = []string{
	ExcelNameSign,
	ExcelEnumSign,
	ExcelUniqueSign,
	ExcelDateSign,
}

var redisConn redis.Conn

// Settings 配置
type Settings struct {
	RedisConn     redis.Conn // Redis连接（结果缓存）[*必填]
	FileDir       string     // 上传文件地址 [*必填]
	UniqueID      string     // 结果ID
	TempHeaderTag string     // 模板表头标签
	OpenValidRow  bool       // 开启自定义行验证
	MaxErrNum     int        // 最大错误数量
	MaxRowNum     int        // 最大数据量
	SheetName     string     // 工作表名称
}

// processor 表格上传对象
type processor struct {
	// params
	uniqueID      string     // 结果ID
	tempHeaderTag string     // 模板表头标签（为空时候表头为结构体里name值，一级简单表头）
	tempHeader    [][]string // 模板表头
	maxErrNum     int        // 最大错误数量（大致数值，未做严格要求）
	maxRowNum     int        // 最大数据量
	sheetName     string     // 表格sheet名称
	fileDir       string     // 上传文件地址
	openValidRow  bool       // 自定义行验证
	headerLength  int        // 表头层数

	// parse content
	file         *excelize.File
	fieldMapping map[string]map[string]string
	body         interface{}
	val          reflect.Value
	uniqueMap    map[int][]string
}

// ProgressStatus 状态
type ProgressStatus int

const (
	PROCESSING ProgressStatus = iota + 1 // 进行中
	PROCESSED                            // 已完成
)

// NewProcessor Excel导入注册
func NewProcessor(st *Settings, body interface{}) (*processor, error) {
	// 设置默认值
	if st.MaxRowNum <= 0 {
		st.MaxRowNum = MaxRowNum
	}
	if st.MaxErrNum <= 0 {
		st.MaxErrNum = MaxErrNum
	}
	if st.SheetName == "" {
		st.SheetName = DefSheetName
	}

	var tempHeader [][]string
	// 获取模板表头
	if st.TempHeaderTag != "" {
		var err error
		tempHeader, err = getTempHeader(st.TempHeaderTag, st.SheetName)
		if err != nil {
			return nil, err
		}
	}
	if st.UniqueID == "" {
		id, err := GetID()
		if err != nil {
			return nil, err
		}
		st.UniqueID = id
	}
	if _, err := st.RedisConn.Do("PING"); err != nil {
		return nil, fmt.Errorf("redis ping failed, err: %v", err)
	}
	redisConn = st.RedisConn

	p := &processor{
		uniqueID:      st.UniqueID,
		tempHeaderTag: st.TempHeaderTag,
		tempHeader:    tempHeader,
		maxErrNum:     st.MaxErrNum,
		maxRowNum:     st.MaxRowNum,
		sheetName:     st.SheetName,
		fileDir:       st.FileDir,
		openValidRow:  st.OpenValidRow,
		headerLength:  len(tempHeader),

		fieldMapping: make(map[string]map[string]string),
		body:         body,
		val:          reflect.Value{},
		uniqueMap:    nil,
	}
	p.val = reflect.ValueOf(body)
	if p.val.Kind() != reflect.Ptr {
		return nil, errors.New("body must be pointer struct")
	}
	// 生成结构体与Excel头映射关系
	p.generateMapping(p.val, "")

	return p, nil
}

// WithTempHeader 添加自定义表头
func (p *processor) WithTempHeader(header [][]string) {
	p.tempHeader = header
}

// Mapping 接口
type Mapping interface {
	ValidationRow(r *ParseResult) error // 验证行数据 [根据需求自己去实现它]
}

// ValidationRow 验证数据
func (p *processor) ValidationRow(r *ParseResult) error {
	return nil
}

// ParseContent 解析内容
func (p *processor) ParseContent() (*ParseResult, error) {
	var (
		err    error
		header [][]string
		rows   [][]string
	)

	defer func() {
		if err != nil {
			// 设置缓存结果
			ret := ParseResult{
				ID:     p.uniqueID,
				Status: PROCESSED,
				ErrMap: make(map[int][]string),
			}
			ret.AddError(-1, err.Error())
			err = ret.Cache()
			if err != nil {
				fmt.Printf("解析错误：%s", err.Error())
			}
		}
	}()
	// 参数验证
	if p.maxRowNum < 1 && p.headerLength < 1 && p.maxErrNum < 1 {
		err = errors.New("配置参数错误")
		return nil, err
	}
	if len(p.tempHeader) <= 0 {
		err = errors.New("未匹配到模板文件")
		return nil, err
	}
	if p.fileDir == "" {
		err = errors.New("未上传文件")
		return nil, err
	}

	rows, header, err = p.getUploadExcel()
	if err != nil {
		return nil, err
	}
	// 验证表头
	err = verifyHeader(p.tempHeader, header)
	if err != nil {
		return nil, err
	}

	return p.rows(rows, header)
}

// 生成结构体与Excel头映射关系
func (p *processor) generateMapping(val reflect.Value, baseField string) {
	switch val.Kind() {
	case reflect.Struct:
	case reflect.Ptr:
		// 当结构体指针或字段指针为空，则创建一个指针指向
		if val.IsNil() {
			newValue := reflect.New(val.Type().Elem())
			val = reflect.NewAt(val.Type().Elem(), unsafe.Pointer(newValue.Pointer()))
		}
		val = val.Elem()
		p.generateMapping(val, baseField)
		return
	default:
		return
	}

	var (
		typ    = val.Type()
		header []string
	)
	for i := 0; i < val.NumField(); i++ {
		fieldName := typ.Field(i).Name
		if baseField != "" {
			fieldName = fmt.Sprintf("%s.%s", baseField, fieldName)
		}
		excel, ok := typ.Field(i).Tag.Lookup(ExcelTag)
		if !ok {
			// 生成嵌套结构体的映射关系
			fieldVal := val.Field(i)
			p.generateMapping(fieldVal, fieldName)
			continue
		}
		var (
			mappingName string
			m           = make(map[string]string, len(signs))
		)
		for _, tag := range signs {
			if tag == ExcelNameSign {
				m[tag] = fieldName
				mappingName, _ = stringMatchExport(excel, regexp.MustCompile(fmt.Sprintf(`%s\((.*?)\)`, ExcelNameSign)))
				header = append(header, mappingName)
				continue
			}
			m[tag], _ = stringMatchExport(excel, regexp.MustCompile(fmt.Sprintf(`%s\((.*?)\)`, tag)))
		}
		key := fmt.Sprintf("%d_%s", i, strings.TrimSpace(mappingName))
		p.fieldMapping[key] = m
	}
	if p.tempHeaderTag == "" {
		p.tempHeader = append(p.tempHeader, header)
		p.headerLength = 1
	}
}

// 数据绑定
func (p *processor) rows(rows, header [][]string) (*ParseResult, error) {
	var (
		ret = &ParseResult{
			ID:     p.uniqueID,
			Total:  len(rows),
			Status: PROCESSING,
			ErrMap: make(map[int][]string),
		}
		count   int
		uniqueM = make(map[string][]string)
		err     error
	)
	// 设置缓存结果
	err = ret.Cache()
	if err != nil {
		return nil, err
	}

	for i := 0; i < len(rows); i++ {
		var (
			dateErrList       []string
			mappingErrList    []string
			parseValueErrList []string
			line              = p.headerLength + i // 行号
			newBodyVal        = reflect.New(p.val.Type().Elem())
			uniqueKey         string
		)
		newBodyVal.Elem().Set(p.val.Elem())

		// 循环行数据
		for colIndex, col := range rows[i] {
			mappingHeader := header[p.headerLength-1][colIndex]
			// 去除列的前后空格
			colVal := strings.TrimSpace(col)
			key := fmt.Sprintf("%d_%s", colIndex, strings.TrimSpace(mappingHeader))
			tagMap, ok := p.fieldMapping[key]
			if !ok {
				continue
			}
			// 判断是否有列唯一标签
			uniqueKey = p.uniqueFormat(uniqueKey, colVal, tagMap)
			// 格式化时间
			dateErrList = p.dateFormat(&colVal, tagMap)
			if len(dateErrList) > 0 {
				ret.AddError(line, dateErrList...)
			}
			// 值映射转换
			mappingErrList = p.mappingFormat(mappingHeader, &colVal, tagMap)
			if len(mappingErrList) > 0 {
				ret.AddError(line, mappingErrList...)
				continue
			}
			// 参数赋值
			parseValueErrList, err = p.parseValue(newBodyVal, tagMap[ExcelNameSign], mappingHeader, colVal)
			if err != nil {
				ret.AddError(line, "参数赋值错误")
				continue
			}
			if len(parseValueErrList) > 0 {
				ret.ErrMap[line] = append(ret.ErrMap[line], parseValueErrList...)
			}
			count += len(dateErrList) + len(mappingErrList) + len(parseValueErrList)
			// 达到最大错误，无需再验证下去了
			if count >= p.maxErrNum {
				return ret, nil
			}
		}
		p.body = newBodyVal.Interface()
		ret.ParseContent = append(ret.ParseContent, p.body)
		// 列唯一性校验
		if uniqueKey != "" {
			uniqueM[uniqueKey] = append(uniqueM[uniqueKey], strconv.Itoa(line))
		}

		// 缓存结果计数+1
		err = ret.Add()
		if err != nil {
			return nil, err
		}
	}
	// 列唯一性校验
	if len(uniqueM) > 0 {
		for _, repeat := range uniqueM {
			if len(repeat) <= 1 {
				continue
			}
			k, _ := strconv.Atoi(repeat[0])
			ret.AddError(k, fmt.Sprintf("第%s行数据重复", strings.Join(repeat, "、")))
		}
	}
	// 自定义参数验证
	err = p.definedValidRow(ret)
	if err != nil {
		return nil, err
	}

	defer func() {
		if err != nil {
			ret.AddError(-1, err.Error())
		}
		ret.Status = PROCESSED
		err = ret.Cache()
		if err != nil {
			return
		}
	}()

	return ret, nil
}

// 自定义行验证
func (p *processor) definedValidRow(ret *ParseResult) error {
	if p.openValidRow {
		inf, ok := p.body.(Mapping)
		if ok {
			// 如果未实现自定义验证接口，调用默认验证
			return inf.ValidationRow(ret)
		}
	}
	return nil
}

// 获取处理数据的excel数据
func (p *processor) getUploadExcel() ([][]string, [][]string, error) {
	// 获取处理excel数据
	rows, err := readExcel(p.fileDir, p.sheetName)
	if err != nil {
		return nil, nil, err
	}
	l := len(rows)
	if l <= 0 {
		return nil, nil, errors.New("文件有效数据为空")
	}
	if p.headerLength > l {
		return nil, nil, errors.New("表格表头错误，请使用正确的表格")
	}
	// excel数据行数限制
	if l > p.maxRowNum {
		return nil, nil, fmt.Errorf("文件数据量超限：%d", p.maxErrNum)
	}
	header := rows[:p.headerLength]
	data := rows[p.headerLength:]

	return data, header, nil
}

// 值映射转换
func (p *processor) mappingFormat(mappingHeader string, col *string, mappingField map[string]string) []string {
	errList := make([]string, 0)
	format, ok := mappingField[ExcelEnumSign]
	if !ok || format == "" {
		return errList
	}
	mappingValues := make(map[string]string)
	formatStr := strings.Split(format, ",")
	for _, format := range formatStr {
		n := strings.SplitN(format, ":", 2)
		if len(n) != 2 {
			continue
		}
		mappingValues[n[0]] = n[1]
	}
	val, ok := mappingValues[*col]
	if ok {
		*col = val
		return errList
	}
	errList = append(errList, fmt.Sprintf("%s单元格存在非法输入", mappingHeader))
	return errList
}

// 参数赋值
func (p *processor) parseValue(val reflect.Value, fieldAddr, mappingHeader, col string) ([]string, error) {
	errList := make([]string, 0)
	fields := strings.Split(fieldAddr, ".")
	if len(fields) == 0 {
		return errList, nil
	}
	for _, field := range fields {
		if val.Kind() == reflect.Ptr {
			val = val.Elem()
		}
		val = val.FieldByName(field)
		errs, err := p.parse(val, col, mappingHeader)
		if err != nil {
			return errList, err
		}
		errList = append(errList, errs...)
	}
	return errList, nil
}

// 解析
func (p *processor) parse(val reflect.Value, col, mappingHeader string) ([]string, error) {
	errList := make([]string, 0)
	var err error
	switch val.Kind() {
	case reflect.String:
		val.SetString(col)
	case reflect.Bool:
		parseBool, err := strconv.ParseBool(col)
		if err != nil {
			errList = append(errList, fmt.Sprintf("%s单元格非法输入,参数非bool类型值", mappingHeader))
		}
		val.SetBool(parseBool)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		var value int64
		if col != "" {
			value, err = strconv.ParseInt(col, 10, 64)
			if err != nil {
				errList = append(errList, fmt.Sprintf("%s单元格非法输入,参数非整形数值", mappingHeader))
			}
		}
		val.SetInt(value)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		var value uint64
		if col != "" {
			value, err = strconv.ParseUint(col, 10, 64)
			if err != nil {
				errList = append(errList, fmt.Sprintf("%s单元格非法输入,参数非整形数值", mappingHeader))
			}
		}
		val.SetUint(value)
	case reflect.Float32, reflect.Float64:
		var value float64
		if col != "" {
			value, err = strconv.ParseFloat(col, 64)
			if err != nil {
				errList = append(errList, fmt.Sprintf("%s单元格非法输入,参数非浮点型数值", mappingHeader))
			}
		}
		val.SetFloat(value)
	case reflect.Struct:
		return errList, nil
	case reflect.Ptr:
		// 初始化指针
		value := reflect.New(val.Type().Elem())
		val.Set(value)
		var errs []string
		errs, err = p.parse(val.Elem(), col, mappingHeader)
		if err != nil {
			break
		}
		errList = append(errList, errs...)
	default:
		return errList, fmt.Errorf("excel column[%s] parseValue unsupported type[%v] mappings", mappingHeader, val.Kind().String())
	}
	return errList, nil
}
